#!/usr/bin/env python
"""
wordtree is a scrabble solver/simulator
"""

import random
from Tkinter import Tk, Canvas, PhotoImage, Frame, NW

class Application(Frame):
    """
    Application class inherits from Tkinter Frame.  Contains gui for wordtree.
    """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        box_space = 30
        boarder = 10

        self.canvas = Canvas(self, width=box_space*15+boarder*2,
                             height=box_space*15+boarder+50)
        self.canvas.pack()
        self.photo = PhotoImage(file='tile.gif')
        self.canvas.create_image(100, 100, anchor=NW, image=self.photo)

class Game:
    """
    This represents a scrabble game.  It has a Board, a TilePile and
    a list of Players.  It will tell the players to make moves.
    Use this class for text mode.
    """
    def __init__(self):
        self.board = Board()
        self.tile_pile = TilePile()
        self.word_list = WordList('enable1.txt')
        self.players = []
        self.players.append(Player(self.tile_pile, self.word_list, self.board))
        self.game_on = True
    def start(self):
        """
        Start the game.
        """
        count = 0
        while self.game_on:
            for player in self.players:
                player.move()
                count += 1
                print self.board
                if count > 1:
                    return

class Player:
    """
    Represents a scrabble player.  Contains AI.
    """
    def __init__(self, tile_pile, word_list, board):
        self.tiles = tile_pile.get(7)
        self.word_list = word_list
        self.board = board
        self.tile_pile = tile_pile
    def move(self):
        clean_board = len(self.board.get_all()) == 0
        if clean_board:
            print 'player move, clean board, tiles: ' + str(self.tiles)
            matches = self.word_list.matches(self.tiles)
            best_val = 0
            for w in matches:
                val = self.check_move(7,7,True,w)
                if val > best_val:
                    best_word = w
                    best_val = val
            self.play_move(7,7,True,best_word)
            print 'best word ' + str(best_word) + ' val ' + str(best_val)
        else:
            # first try for horizontal words
            best_word = ''
            best_val = 0
            best_i = 0
            best_j = 0
            best_hor = True
            letters = []
            next_letters = self.board.get_row(0)
            for i in xrange(self.board.rows):
                prev_letters = letters
                letters = next_letters
                if i+1<self.board.rows:
                    next_letters = self.board.get_row(i+1)
                else:
                    next_letters = []
                if len(prev_letters)+len(letters)+len(next_letters) == 0:
                    continue
                matches = self.word_list.matches(self.tiles+letters)
                for w in matches:
                    for j in xrange(self.board.cols-len(w)+1):
                        if not self.quick_check(i,j,True,w):
                            continue
                        val = self.check_move(i,j,True,w)
                        if val > best_val:
                            best_val = val
                            best_word = w
                            best_i = i
                            best_j = j
                            best_hor = True
                            print 'best Hor ' + str(best_i) +\
                                    ' ' + str(best_j) +\
                                    ' ' + str(best_word) +\
                                    ' ' + str(best_val)
            # now try virticle
            letters = []
            next_letters = self.board.get_col(0)
            for j in xrange(self.board.cols):
                prev_letters = letters
                letters = next_letters
                if j+1<self.board.cols:
                    next_letters = self.board.get_col(j+1)
                else:
                    next_letters = []
                if len(prev_letters)+len(letters)+len(next_letters) == 0:
                    continue
                matches = self.word_list.matches(self.tiles+letters)
                for w in matches:
                    for i in xrange(self.board.rows-len(w)+1):
                        if not self.quick_check(i,j,False,w):
                            continue
                        val = self.check_move(i,j,False,w)
                        if val > best_val:
                            best_val = val
                            best_word = w
                            best_i = i
                            best_j = j
                            best_hor = False
                            print 'best Vir ' + str(best_i) + ' ' +\
                                    str(best_j) + ' ' + str(best_word) +\
                                    ' ' + str(best_val)
            self.play_move(best_i,best_j,best_hor,best_word)
        self.tiles += self.tile_pile.get(7-len(self.tiles))
        print 'new tiles ' + str(self.tiles)
    def quick_check(self,i,j,hor,word):
        '''
        Run this one before check_move
        Make sure this has some chance at being a legal move.  Check to make sure the move is
        not floating in space.  Check to make sure it is not trying to play letters over 
        different letters.
        '''
        # make sure there are no letters before or after the move
        if hor:
            if j > 0 and\
                    self.board.board[i][j-1] not in self.board.ignore or \
                    j + len(word) < self.board.cols and\
                    self.board.board[i][j+len(word)] not in self.board.ignore:
                return False
        else:
            if i > 0 and\
                    self.board.board[i-1][j] not in self.board.ignore or \
                i + len(word) < self.board.rows and\
                self.board.board[i+len(word)][j] not in self.board.ignore:
                return False
        touching = False
        all_touching = True
        for offset in xrange(len(word)):
            if hor:
                ii = i
                jj = j + offset
            else:
                ii = i + offset
                jj = j
            if self.board.board[ii][jj] in self.board.ignore:
                all_touching = False
            else:
                if not self.board.board[ii][jj][0] == word[offset]:
                    return False
                else:
                    touching = True
        if all_touching:
            return False
        if touching:
            return True
        # need to check that we are not floating in space
        for offset in xrange(len(word)):
            if hor:
                ii = i+1
                jj = j + offset
            else:
                ii = i + offset
                jj = j+1
            if not self.board.board[ii][jj] in self.board.ignore:
                return True
        for offset in xrange(len(word)):
            if hor:
                ii = i-1
                jj = j + offset
            else:
                ii = i + offset
                jj = j-1
            if not self.board.board[ii][jj] in self.board.ignore:
                return True
        return False

    def check_move(self,i,j,hor,word):
        vals = self.word_to_values(word)
        val = 0
        multiplier = 1
        t = list(self.tiles)
        for offset in xrange(len(word)):
            if hor:
                ii = i
                jj = j + offset
            else:
                ii = i + offset
                jj = j
            if self.board.board[ii][jj] in self.board.ignore:
                # we are using a tile
                if word[offset] in t:
                    t.pop(t.index(word[offset]))
                elif '*' in t:
                    t.pop(t.index('*'))
                    vals[offset] = 0
                else:
                    # not enough tiles
                    return 0
                if self.board.board[ii][jj] == 'tw':
                    val += vals[offset]
                    multiplier *= 3
                elif self.board.board[ii][jj] == 'dw':
                    val += vals[offset]
                    multiplier *= 2
                elif self.board.board[ii][jj] == 'tl':
                    val += vals[offset]*3
                elif self.board.board[ii][jj] == 'dl':
                    val += vals[offset]*2
                else:
                    val += vals[offset]
            else:
                if not self.board.board[ii][jj][0] == word[offset]:
                    return 0
                elif len(self.board.board[ii][jj]) == 2 and\
                        self.board.board[ii][jj][1] == '*':
                    val += 0
                else:
                    val += vals[offset]
        return val*multiplier
    def play_move(self,i,j,hor,word):
        for offset in xrange(len(word)):
            if hor:
                ii = i
                jj = j + offset
            else:
                ii = i + offset
                jj = j
            if not self.board.board[ii][jj] in self.board.ignore:
                continue
            self.board.board[ii][jj] = word[offset]
            if word[offset] in self.tiles:
                self.tiles.pop(self.tiles.index(word[offset]))
            elif '*' in self.tiles:
                self.tiles.pop(self.tiles.index('*'))
                self.board.board[ii][jj] += '*'
            else:
                raise Exception('we do not have the tiles for this move')
    def word_to_values(self,word):
        out = []
        for c in word:
            if c in ['*']:
                out.append(0)
            elif c in ['a','e','i','o','r','s','t']:
                out.append(1)
            elif c in ['d','l','n','u']:
                out.append(2)
            elif c in ['g','h','y']:
                out.append(3)
            elif c in ['b','c','f','m','p','w']:
                out.append(4)
            elif c in ['k','v']:
                out.append(5)
            elif c in ['x']:
                out.append(8)
            elif c in ['j','q','z']:
                out.append(10)
        return out

class WordList:
    def __init__(self,filename):
        f = open(filename)
        self.word_list = f.readlines()
        f.close()
        for i in xrange(len(self.word_list)):
            self.word_list[i] = self.word_list[i].strip()
    def matches(self,tiles):
        out = []
        wild = '*' in tiles
        for w in self.word_list:
            good = True
            # if there are no wild tiles, then we can
            # eliminate a lot of words quickly
            if not wild:
                for c in w:
                    if not c in tiles:
                        good = False
                        break
            if not good:
                continue
            # now we have a word with matching letters in tiles, 
            # but we need to make sure there are enough tiles
            # to cover all the letters the word
            t = list(tiles)
            for c in w:
                if c in t:
                    t.pop(t.index(c))
                elif wild and '*' in t:
                    t.pop(t.index('*'))
                else:
                    good = False
                    break
            if good:
                out.append(w)
        return out

class TilePile:
    def __init__(self):
        self.tiles = \
        ['a'] * 9 + \
        ['b'] * 2 + \
        ['c'] * 2 + \
        ['d'] * 5 + \
        ['e'] * 13 + \
        ['f'] * 2 + \
        ['g'] * 3 + \
        ['h'] * 4 + \
        ['i'] * 8 + \
        ['j'] * 1 + \
        ['k'] * 1 + \
        ['l'] * 4 + \
        ['m'] * 2 + \
        ['n'] * 5 + \
        ['o'] * 8 + \
        ['p'] * 2 + \
        ['q'] * 1 + \
        ['r'] * 6 + \
        ['s'] * 5 + \
        ['t'] * 7 + \
        ['u'] * 4 + \
        ['v'] * 2 + \
        ['w'] * 2 + \
        ['x'] * 1 + \
        ['y'] * 2 + \
        ['z'] * 1 + \
        ['*'] * 2
        random.shuffle(self.tiles)
    def get(self,n=1):
        out = self.tiles[0:n]
        self.tiles = self.tiles[n:]
        return out
    def size(self):
        return len(self.tiles)

class Board:
    def __init__(self):
        self.rows = 15
        self.cols = 15
        self.board = [['.' for i in range(self.cols)]
                for j in range(self.rows) ]
        self.add_sym(0,3,'tw')
        self.add_sym(1,2,'dl')
        self.add_sym(0,6,'tl')
        self.add_sym(1,5,'dw')
        self.add_sym(2,4,'dl')
        self.add_sym(3,3,'tl')
        self.add_sym(3,7,'dw')
        self.add_sym(4,6,'dl')
        self.add_sym(5,5,'tl')
        self.board[7][7] = '*'
        self.ignore = ['dl','tl','dw','tw','.','*']
    def __str__(self):
        s = ''
        for line in self.board:
            for c in line:
                s = s + c.ljust(2)
            s = s + '\n'
        s = s + '\n'
        return s
    def add_sym(self,r,c,v):
        '''symmetrically add a value to the board at row r and col c'''
        self.board[r][c] = v
        self.board[c][r] = v
        self.board[self.rows - r - 1][self.cols - c - 1] = v
        self.board[self.rows - c - 1][self.cols - r - 1] = v
        self.board[r][self.cols - c - 1] = v
        self.board[c][self.cols - r - 1] = v
        self.board[self.rows - r - 1][c] = v
        self.board[self.rows - c - 1][r] = v
    def get_all(self):
        letters = []
        for row in self.board:
            for c in row:
                if c not in self.ignore:
                    letters.append(c)
        return letters
    def get_row(self,i):
        letters = []
        row = self.board[i]
        for c in row:
            if c not in self.ignore:
                letters.append(c[0])
        return letters
    def get_col(self,j):
        letters = []
        for i in xrange(self.rows):
            c = self.board[i][j]
            if c not in self.ignore:
                letters.append(c[0])
        return letters



class Node:
    def __init__(self,fragment):
        self.fragment = fragment
    def __str__(self):
        return self.fragment

class Wordtree:
    def __init__(self):
        self.list = []
    def init_from_file(self,filename):
        dict_file = open(filename,'r')
        count = 0
        for line in dict_file:
            word = line.split()
            if len(word) == 0:
                continue
            word = word[0].upper()
            if not word.isalpha():
                continue
            self.add_word(word)
            count = count + 1
            if count > 2:
                break
        dict_file.close()
    def add_word(self,word):
        print 'word ' + str(word)
        for l in range(1,len(word)+1):
            for o in range(len(word)-l+1):
                fragment = word[o:o+l]
                node = Node(fragment)
                if not node in self.list:
                    self.list.append(node)
                    for t in self.list:
                        print str(t) + ' ',
                    print
                else:
                    print 'already have ' + fragment

def main():
    launch_gui = False
    if launch_gui:
        root = Tk()
        app = Application(master=root)
        app.mainloop()
    else:
        g = Game()
        g.start()

if __name__ == '__main__':
    main()

